/*-
 * Copyright (c) 2012-2013 Shang-Rong Cai. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $ MTBLAS: isa_op3.c    Rev. 0.0.11    2013-11-07T16:58+08:00 $
 */

#include <math.h>
#include <complex.h>

#include <exec_isa.h>

/* Execute Opcode */
void op3_eoc2_op(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    void (*opcode)(operand_t*, operand_t*, operand_t*, operand_t*);

    opcode = ra->addr;

    (*opcode)((operand_t*) rb->addr, (operand_t*) rc->addr, NULL, NULL);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


/* %c = %a + %b */
void op3_add_ff(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float *rg_a;
    float *rg_b;
    float *rg_c;

    rg_a = (float*) ra->addr;
    rg_b = (float*) rb->addr;
    rg_c = (float*) rc->addr;

    *rg_c = *rg_a + *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_add_df(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double *rg_a;
    double *rg_b;
    double *rg_c;

    rg_a = (double*) ra->addr;
    rg_b = (double*) rb->addr;
    rg_c = (double*) rc->addr;

    *rg_c = *rg_a + *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_add_lf(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double *rg_a;
    long double *rg_b;
    long double *rg_c;

    rg_a = (long double*) ra->addr;
    rg_b = (long double*) rb->addr;
    rg_c = (long double*) rc->addr;

    *rg_c = *rg_a + *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_add_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;

    *rg_c = *rg_a + *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_add_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;

    *rg_c = *rg_a + *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_add_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;

    *rg_c = *rg_a + *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


/* %c = %a * %b */
void op3_mul_ff(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float *rg_a;
    float *rg_b;
    float *rg_c;

    rg_a = (float*) ra->addr;
    rg_b = (float*) rb->addr;
    rg_c = (float*) rc->addr;

    *rg_c = *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_mul_df(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double *rg_a;
    double *rg_b;
    double *rg_c;

    rg_a = (double*) ra->addr;
    rg_b = (double*) rb->addr;
    rg_c = (double*) rc->addr;

    *rg_c = *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_mul_lf(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double *rg_a;
    long double *rg_b;
    long double *rg_c;

    rg_a = (long double*) ra->addr;
    rg_b = (long double*) rb->addr;
    rg_c = (long double*) rc->addr;

    *rg_c = *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_mul_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;

    *rg_c = *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_mul_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;

    *rg_c = *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_mul_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;

    *rg_c = *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


/* %c += %a * %b or %c = %a * %b + %c */
void op3_fmac_ff(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float *rg_a;
    float *rg_b;
    float *rg_c;

    rg_a = (float*) ra->addr;
    rg_b = (float*) rb->addr;
    rg_c = (float*) rc->addr;

    *rg_c += *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_fmac_df(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double *rg_a;
    double *rg_b;
    double *rg_c;

    rg_a = (double*) ra->addr;
    rg_b = (double*) rb->addr;
    rg_c = (double*) rc->addr;

    *rg_c += *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_fmac_lf(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double *rg_a;
    long double *rg_b;
    long double *rg_c;

    rg_a = (long double*) ra->addr;
    rg_b = (long double*) rb->addr;
    rg_c = (long double*) rc->addr;

    *rg_c += *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_fmac_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;

    *rg_c += *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_fmac_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;

    *rg_c += *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_fmac_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;

    *rg_c += *rg_a * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


/* %c += conj(%a) * %b or %c = conj(%a) * %b + %c */
void op3_conjfmac_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;

    *rg_c += conjf(*rg_a) * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_conjfmac_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;

    *rg_c += conj(*rg_a) * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_conjfmac_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;

    *rg_c += conjl(*rg_a) * *rg_b;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


/* %c += %a * conj(%b) or %c = %a * conj(%b) + %c */
void op3_conjbfmac_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;

    *rg_c += *rg_a * conjf(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_conjbfmac_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;

    *rg_c += *rg_a * conjf(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_conjbfmac_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;

    *rg_c += *rg_a * conjf(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


/* %c += conj(%a) * conj(%b) or %c = conj(%a) * conj(%b) + %c */
void op3_conjabfmac_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;

    *rg_c += conjf(*rg_a) * conjf(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_conjabfmac_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;

    *rg_c += conj(*rg_a) * conj(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_conjabfmac_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;

    *rg_c += conjl(*rg_a) * conjl(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


/* %c = abs(%a) + abs(%b) */
void op3_fabsadd_ff(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float *rg_a;
    float *rg_b;
    float *rg_c;

    rg_a = (float*) ra->addr;
    rg_b = (float*) rb->addr;
    rg_c = (float*) rc->addr;

    *rg_c = fabsf(*rg_a) + fabsf(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_fabsadd_df(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double *rg_a;
    double *rg_b;
    double *rg_c;

    rg_a = (double*) ra->addr;
    rg_b = (double*) rb->addr;
    rg_c = (double*) rc->addr;

    *rg_c = fabs(*rg_a) + fabs(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_fabsadd_lf(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double *rg_a;
    long double *rg_b;
    long double *rg_c;

    rg_a = (long double*) ra->addr;
    rg_b = (long double*) rb->addr;
    rg_c = (long double*) rc->addr;

    *rg_c = fabsl(*rg_a) + fabsl(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}

void op3_cabsadd_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;

    *rg_c = cabsf(*rg_a) + cabsf(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_cabsadd_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;

    *rg_c = cabs(*rg_a) + cabs(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}


void op3_cabsadd_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;

    *rg_c = cabsl(*rg_a) + cabs(*rg_b);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
}
